package com.ruoyix.weblog.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.io.FileUtil;
import com.ruoyix.common.core.domain.R;
import com.ruoyix.common.helper.LoginHelper;
import com.ruoyix.common.utils.StringUtils;
import com.ruoyix.common.core.page.TableDataInfo;
import com.ruoyix.common.core.domain.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyix.weblog.config.VideoStorageConfig;
import com.ruoyix.weblog.domain.BlogArticle;
import com.ruoyix.weblog.domain.UploadPO;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import org.bytedeco.javacv.FFmpegFrameGrabber;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import com.ruoyix.weblog.domain.bo.BlogVideosBo;
import com.ruoyix.weblog.domain.vo.BlogVideosVo;
import com.ruoyix.weblog.domain.BlogVideos;
import com.ruoyix.weblog.mapper.BlogVideosMapper;
import com.ruoyix.weblog.service.IBlogVideosService;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Collection;

/**
 * 视频管理Service业务层处理
 *
 * @author ruoyi
 * @date 2024-09-06
 */
@RequiredArgsConstructor
@Service
public class BlogVideosServiceImpl implements IBlogVideosService {

    private final BlogVideosMapper baseMapper;

    private final VideoConversionService videoConversionService;


    private String uploadBasePath;
    private String videoTempDir;
    private String videoStorageDir;
    private String m3u8Dir;
    private final VideoStorageConfig webConfig;
    @PostConstruct
    public void init() {
        String os = System.getProperty("os.name").toLowerCase();
        uploadBasePath = os.contains("win") ?
            webConfig.getUploadPath().getWindows() :
            webConfig.getUploadPath().getLinux();

        videoTempDir = uploadBasePath + webConfig.getVideo().getTempDir();
        videoStorageDir = uploadBasePath + webConfig.getVideo().getStorageDir();
        m3u8Dir = webConfig.getVideo().getM3u8Dir();
    }
    /**
     * 查询视频管理
     */
    @Override
    public BlogVideosVo queryById(Long id){
        return baseMapper.selectVoById(id);
    }

    /**
     * 查询视频管理列表
     */
    @Override
    public TableDataInfo<BlogVideosVo> queryPageList(BlogVideosBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<BlogVideos> lqw = buildQueryWrapper(bo);
        Page<BlogVideosVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    /**
     * 查询视频管理列表
     */
    @Override
    public List<BlogVideosVo> queryList(BlogVideosBo bo) {
        LambdaQueryWrapper<BlogVideos> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<BlogVideos> buildQueryWrapper(BlogVideosBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<BlogVideos> lqw = Wrappers.lambdaQuery();
        lqw.eq(bo.getCatalogId() != null, BlogVideos::getCatalogId, bo.getCatalogId());
        lqw.eq(StringUtils.isNotBlank(bo.getVideoTitle()), BlogVideos::getVideoTitle, bo.getVideoTitle());
        lqw.eq(StringUtils.isNotBlank(bo.getMd5()), BlogVideos::getMd5, bo.getMd5());
        lqw.eq(StringUtils.isNotBlank(bo.getDescription()), BlogVideos::getDescription, bo.getDescription());
        lqw.eq(StringUtils.isNotBlank(bo.getUrl()), BlogVideos::getUrl, bo.getUrl());
        lqw.eq(StringUtils.isNotBlank(bo.getDuration()), BlogVideos::getDuration, bo.getDuration());
        lqw.eq(StringUtils.isNotBlank(bo.getThumbnailUrl()), BlogVideos::getThumbnailUrl, bo.getThumbnailUrl());
        lqw.eq(bo.getStatus() != null, BlogVideos::getStatus, bo.getStatus());
        lqw.eq(bo.getVideoType() != null, BlogVideos::getVideoType, bo.getVideoType());
        lqw.eq(StringUtils.isNotBlank(bo.getPlayType()), BlogVideos::getPlayType, bo.getPlayType());
        lqw.eq(bo.getLikeCount() != null, BlogVideos::getLikeCount, bo.getLikeCount());
        lqw.eq(bo.getViewCount() != null, BlogVideos::getViewCount, bo.getViewCount());
        lqw.eq(bo.getDelFlag() != null, BlogVideos::getDelFlag, bo.getDelFlag());
        return lqw;
    }

    /**
     * 新增视频管理
     */
    @Override
    public Boolean insertByBo(BlogVideosBo bo) {
        BlogVideos add = BeanUtil.toBean(bo, BlogVideos.class);
        validEntityBeforeSave(add);
        add.setUserId(LoginHelper.getUserId());
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    /**
     * 修改视频管理
     */
    @Override
    public Boolean updateByBo(BlogVideosBo bo) {
        BlogVideos update = BeanUtil.toBean(bo, BlogVideos.class);
        validEntityBeforeSave(update);
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(BlogVideos entity){
        //TODO 做一些数据校验,如唯一约束
    }

    /**
     * 批量删除视频管理
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if(isValid){
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteBatchIds(ids) > 0;
    }

    @Override
    public R<String> checkExists(String fileMd5) {
        String fileUploadDir = videoStorageDir + fileMd5;
        String videoFile = fileUploadDir + File.separator + fileMd5 + ".mp4";
        File file = new File(videoFile);
        return file.exists() ? R.ok("文件已存在，秒传成功") : R.ok();
    }

    @Override
    public R<String> uploadChunk(UploadPO uploadPO) {
        MultipartFile fileData = uploadPO.getFile();
        int index = uploadPO.getChunkNumber();
        int totalChunk = uploadPO.getTotalChunks();
        String fileMd5 = uploadPO.getIdentifier();

        // 目标文件路径
        String videoFile = videoStorageDir + fileMd5 + File.separator + fileMd5 + ".mp4";
        if (new File(videoFile).exists()) {
            return R.ok("文件已存在，秒传成功");
        }

        // 分片存储
        String newName = fileMd5 + index + ".tem";
        File uploadFile = new File(videoTempDir + fileMd5, newName);
        if (!uploadFile.getParentFile().exists()) {
            uploadFile.getParentFile().mkdirs();
        }

        try {
            fileData.transferTo(uploadFile);
            int currentChunkCount = getChunkCount(videoTempDir + fileMd5);

            if (totalChunk == currentChunkCount) {
                merge(fileMd5, videoTempDir, videoStorageDir);
                String m3u8DirPath = videoStorageDir + m3u8Dir + File.separator + fileMd5;
                new File(m3u8DirPath).mkdirs();

                String mp4File = videoStorageDir + fileMd5 + File.separator + fileMd5 + ".mp4";
                long duration = getVideoDuration(mp4File);

                String os = System.getProperty("os.name").toLowerCase();
                if (os.contains("win")) {
                    videoConversionService.convertToM3U8ForWindows(mp4File, m3u8DirPath, duration, fileMd5);
                } else {
                    videoConversionService.convertToM3U8ForLinux(mp4File, m3u8DirPath, duration, fileMd5);
                }
                return R.ok("上传完成", fileMd5);
            }
            return R.ok();
        } catch (Exception e) {
            cleanTempFiles(fileMd5);
            return R.fail("上传失败: " + e.getMessage());
        }
    }

    private void cleanTempFiles(String fileMd5) {
        FileUtil.del(videoTempDir + fileMd5);
        FileUtil.del(videoStorageDir + fileMd5);
        FileUtil.del(videoStorageDir + m3u8Dir + File.separator + fileMd5);
    }

    /**
     * 获取当前目录下的分片文件数量
     *
     * @param directoryPath
     * @return
     */
    private int getChunkCount(String directoryPath) {
        File directory = new File(directoryPath);
        if (!directory.exists() || !directory.isDirectory()) {
            return 0;
        }
        File[] files = directory.listFiles((dir, name) -> name.endsWith(".tem"));
        return files != null ? files.length : 0;
    }

    /**
     * 合并分片
     *
     * @param uuid md5
     * @return
     */
    public void merge(String uuid, String fileUploadTempDir, String fileUploadDir) throws IOException, InterruptedException {
        File dirFile = new File(fileUploadTempDir + File.separator + uuid);
        //分片上传的文件已经位于同一个文件夹下，方便寻找和遍历（当文件数大于十的时候记得排序用冒泡排序确保顺序是正确的）
        String[] fileNames = dirFile.list();
        Arrays.sort(fileNames, (o1, o2) -> {
            int i1 = Integer.parseInt(o1.substring(o1.indexOf(uuid) + uuid.length()).split("\\.tem")[0]);
            int i2 = Integer.parseInt(o2.substring(o2.indexOf(uuid) + uuid.length()).split("\\.tem")[0]);
            return i1 - i2;
        });

        //创建空的合并文件,以未见md5为文件名
        File targetFile = new File(fileUploadDir+File.separator+uuid+File.separator, uuid + ".mp4");
        if (!targetFile.getParentFile().exists()) {
            targetFile.getParentFile().mkdirs();
        }
        RandomAccessFile writeFile = new RandomAccessFile(targetFile, "rw");
        long position = 0;
        for (String fileName : fileNames) {
            System.out.println(fileName);
            File sourceFile = new File(fileUploadTempDir + File.separator + uuid, fileName);
            RandomAccessFile readFile = new RandomAccessFile(sourceFile, "rw");
            int chunksize = 1024 * 3;
            byte[] buf = new byte[chunksize];
            writeFile.seek(position);
            int byteCount;
            while ((byteCount = readFile.read(buf)) != -1) {
                if (byteCount != chunksize) {
                    byte[] tempBytes = new byte[byteCount];
                    System.arraycopy(buf, 0, tempBytes, 0, byteCount);
                    buf = tempBytes;
                }
                writeFile.write(buf);
                position = position + byteCount;
            }
            readFile.close();
        }
        writeFile.close();
        Thread.sleep(1000);
        FileUtil.del(dirFile);
    }


    public static long getVideoDuration(String videoFilePath) throws Exception {
        FFmpegFrameGrabber grabber = new FFmpegFrameGrabber(videoFilePath);
        grabber.start();
        long duration = grabber.getLengthInTime() / 1000; // in milliseconds
        grabber.stop();
        grabber.release();


        int totalSegments = calculateTotalSegments(duration); // 计算切片数量
        long segmentDuration = duration / totalSegments;
        return segmentDuration;
    }

    private static int calculateTotalSegments(long totalDuration) {
        // 根据每个切片时长为一分钟计算切片数量
        int segmentLengthInSeconds = 15; // 每个切片时长为一分钟（60秒）
        return (int) Math.ceil(totalDuration / (double) (segmentLengthInSeconds * 1000));
    }


    public static String getFilePath(){
        String os = System.getProperty("os.name").toLowerCase();
        if (os.contains("win")) {
            return "D:"+File.separator+"uploadTest" + File.separator;
        } else {
            return "/ruoyi-x-blog/video/";
        }
    }



}
